module;
#include <vector>

export module Code_18_4_1;


class Coord
{
private:
    int x, y;
public:
    Coord(int i1, int i2)
        : x(i1), y(i2)
    {
    }
    friend Coord operator- (Coord const& c1, Coord const& c2)
    {
        return Coord(c1.x - c2.x, c1.y - c2.y);
    }
    Coord abs()
    {
        return Coord(std::abs(x), std::abs(y));
    }
};


// concrete geometric object class Circle
// - \bfseries not derived from any class
class Circle
{
public:
    void draw() const {}
    Coord center_of_gravity() const { return { 1,1 }; }
    //...
};

// concrete geometric object class Line
// - \bfseries not derived from any class
class Line
{
public:
    void draw() const {}
    Coord center_of_gravity() const { return { 1,1 }; }
    //...
};
//...



template<typename T>
concept GeoObj = requires(T x) 
{
    { x.draw() } -> std::convertible_to<void>;
    { x.center_of_gravity() } -> std::convertible_to <Coord>;
    //...
};

// draw any GeoObj
template<typename T>
    requires GeoObj<T>
void myDraw(T const& obj)
{
    obj.draw();    // call draw() according to type of object
}

// compute distance of center of gravity between two GeoObjs
template<typename T1, typename T2>
    requires GeoObj<T1> && GeoObj<T2>
Coord distance(T1 const& x1, T2 const& x2)
{
    Coord c = x1.center_of_gravity() - x2.center_of_gravity();
    return c.abs();  // return coordinates as absolute values
}

// draw homogeneous collection of GeoObjs
template<typename T>
    requires GeoObj<T>
void drawElems(std::vector<T> const& elems)
{
    for (unsigned i = 0; i < elems.size(); ++i) {
        elems[i].draw();    // call draw() according to type of element
    }
}

export
namespace Code_18_4_1 {

    void run()
    {
        Line l;
        Circle c, c1, c2;

        myDraw(l);        // myDraw<Line>(GeoObj\&) => Line::draw()
        myDraw(c);        // myDraw<Circle>(GeoObj\&) => Circle::draw()

        distance(c1, c2);  // distance<Circle,Circle>(GeoObj1\&,GeoObj2\&)
        distance(l, c);    // distance<Line,Circle>(GeoObj1\&,GeoObj2\&)

        // std::vector<GeoObj*> coll;    // ERROR: no heterogeneous collection possible
        std::vector<Line> coll;   // OK: homogeneous collection possible
        coll.push_back(l);        // insert line
        drawElems(coll);          // draw all lines
    }
}
